package cn.tedu.lbs.article.web.service;

import cn.tedu.lbs.article.web.repository.ActionESRepository;
import cn.tedu.lbs.article.web.repository.ArticleESRepository;
import cn.tedu.lbs.article.web.repository.ArticleRepository;
import cn.tedu.lbs.article.web.utils.AuthenticationContextUtil;
import cn.tedu.lbs.marketing.api.MarketingApi;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.tarena.lbs.base.common.utils.Asserts;
import com.tarena.lbs.base.protocol.exception.BusinessException;
import com.tarena.lbs.base.protocol.pager.PageResult;
import com.tarena.lbs.common.content.utils.SequenceGenerator;
import com.tarena.lbs.common.passport.principle.UserPrinciple;
import com.tarena.lbs.pojo.content.entity.ActionSearchEntity;
import com.tarena.lbs.pojo.content.entity.ArticleSearchEntity;
import com.tarena.lbs.pojo.content.param.ArticleActionParam;
import com.tarena.lbs.pojo.content.param.ArticleContentParam;
import com.tarena.lbs.pojo.content.query.ArticleQuery;
import com.tarena.lbs.pojo.content.vo.ArticleActionPageResultVO;
import com.tarena.lbs.pojo.content.vo.ArticleActionVO;
import com.tarena.lbs.pojo.content.vo.ArticleVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ArticleService {
    @Autowired
    private ArticleRepository articleRepository;
    @Autowired
    private ArticleESRepository articleESRepository;
    @DubboReference
    private MarketingApi marketingApi;
    @Autowired
    private ActionESRepository actionESRepository;
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    public PageResult<ArticleVO> pageList(ArticleQuery articleQuery) throws BusinessException {
        //1.做一个假的 缺少分页数据的page对象
        PageResult<ArticleVO> voPage=initPage();
        //2.根据条件中 source的值 2表示接口访问来源是后台前端  1表示接口访问的来源是前台小程序前端
        int source=articleQuery.getSource();
        if(source==1){
            //补充条件 前台小程序查询 label非空 lat lon非空 source去除 status=1
            articleQuery.setSource(null);
            articleQuery.setArticleStatus(Arrays.asList(1));
        }else if(source==2){
            //补充条件 设置当前登录用户的userId 作为文章作者条件补充
            Integer userId=getUserId();
            articleQuery.setUserId(userId);
        }
        //3.读数据交给仓储层
        List<ArticleSearchEntity> entities=
                articleRepository.searchArticles(articleQuery);
        //4.非空转化vo
        List<ArticleVO> vos=null;
        if (CollectionUtils.isNotEmpty(entities)){
            vos=entities.stream().map(entity->{
                ArticleVO vo=new ArticleVO();
                BeanUtils.copyProperties(entity,vo);
                return vo;
            }).collect(Collectors.toList());
        }
        voPage.setObjects(vos);
        return voPage;
    }

    private Integer getUserId() throws BusinessException {
        //拿到上游传递的认证对象
        UserPrinciple userPrinciple = AuthenticationContextUtil.get();
        //断言 非空
        Asserts.isTrue(userPrinciple==null,new BusinessException("-2","用户认证解析失败"));
        //返回id
        return userPrinciple.getId();
    }

    private PageResult<ArticleVO> initPage() {
        PageResult<ArticleVO> voPage=new PageResult<>(10,1);
        voPage.setTotal(100l);
        return voPage;
    }

    public void insert(ArticleContentParam articleContentParam) {
        //1.param转化成entity
        ArticleSearchEntity entity=assembleArticleEntity(articleContentParam);
        //2.调用repository save 文章数据
        articleESRepository.save(entity);
        //远程调用绑定图片 否则 图片会在定时检查冗余图片中被当做 垃圾删除
    }

    private ArticleSearchEntity assembleArticleEntity(ArticleContentParam articleContentParam) {
        //准备一个封装entity
        ArticleSearchEntity entity=new ArticleSearchEntity();
        //大部分param属性可以直接拷贝的
        BeanUtils.copyProperties(articleContentParam,entity);
        //1.文档的id 文章的id
        entity.setId(SequenceGenerator.sequence());
        //2.缺少作者id
        UserPrinciple userPrinciple = AuthenticationContextUtil.get();
        entity.setUserId(userPrinciple.getId());
        entity.setArticleUsername(userPrinciple.getNickname());
        //3.createTime updateTime
        entity.setCreateTime(new Date());
        entity.setUpdateTime(new Date());
        //4.地理位置 拼接latitude,longitude
        entity.setLocation(articleContentParam.getLatitude()+","+articleContentParam.getLongitude());
        //5.点赞 收藏 访问 转发 评论 等 后续放到redis中计算
        return entity;
    }

    public Set<String> getArticleLabels(ArticleQuery articleQuery) {
        //1.根据业务需求 和入参 组织一个去重的查询 文档集合
        List<ArticleSearchEntity> entities=searchLabels(articleQuery);
        //2.将entities 只取每个元素的一个articleLabel属性组织集合返回
        Set<String> labels=assembleLabels(entities);
        return labels;
    }

    private Set<String> assembleLabels(List<ArticleSearchEntity> entities) {
        Set<String> labels=null;
        //查询结果不为空的时候 才封装数据
        if (CollectionUtils.isNotEmpty(entities)){
            labels=entities.stream().map(entity->{
                //把每个文档中的标签属性作为返回值
                return entity.getArticleLabel();
            }).collect(Collectors.toSet());
        }
        return labels;
    }

    private List<ArticleSearchEntity> searchLabels(ArticleQuery articleQuery) {
        //搜索的逻辑 利用2个字条件 做must 拼接 一个是范围 一个是状态 同时去重
        return articleRepository.searchLabels(articleQuery);
    }

    public ArticleVO detail(Integer id) throws BusinessException {
        //1.解析拿到userId
        Integer userId = getUserId();
        //2.读取es中的文档对象
        ArticleSearchEntity entity = articleESRepository.findById(id).get();
        //[补充]2.1 调用数据层 将现有的access访问count 叠加
        //参数 behave动作类型,文章id,现有的值
        Integer accessCount=countAdd(3,id,entity.getAccessCount());
        //3.条件判断 文档中的文章数据 是否存在活动的绑定id
        Integer activityId=entity.getActivityId();
        if (activityId!=null){
        //3.1存在 远程调用marketing 根据反馈 决定是否携带activityId 到vo展示
            //4 远程调用
            boolean visible=marketingApi.activityIsVisible(userId,activityId);
            //5 判断 如果不可见 需要将entity中的activityId 删除
            if (!visible){
                log.info("当前用户:{},访问文章:{},不属于活动目标人群,不携带活动id:{}",userId,id,activityId);
                entity.setActivityId(null);
            }
        }
        //3.2不存在活动id 正常使用entity 转化vo
        ArticleVO vo=new ArticleVO();
        BeanUtils.copyProperties(entity,vo);
        republishCounts(vo);
        assembleAndSaveAction(userId,entity,3);
        return vo;
    }
    //从es中查询的entity 没有携带正确的count值 需要读取redis数据单独封装
    private void republishCounts(ArticleVO vo) {
        HashOperations<String, String, String> opsForHash = stringRedisTemplate.opsForHash();
        //三个数字都读 可能读取到属性值是null
        String key="article:count:"+vo.getId();
        vo.setLikeCount(Integer.valueOf(opsForHash.get(key,"1")==null?"0":opsForHash.get(key,"1")));
        vo.setFavoriteCount(Integer.valueOf(opsForHash.get(key,"2")==null?"0":opsForHash.get(key,"2")));
        vo.setAccessCount(Integer.valueOf(opsForHash.get(key,"3")==null?"0":opsForHash.get(key,"3")));
    }

    private Integer countAdd(Integer behave, Integer id, Integer nowCount) throws BusinessException {
        //redis 设计key值 代表的是文章 field 存储 behave  value count
        HashOperations<String, String, String> opsForHash = stringRedisTemplate.opsForHash();
        String key="article:count:"+id;
        //调用客户端 对现有的数据 做自增处理 hincrby key behave 1
        Long increment = opsForHash.increment(key, behave + "", 1);
        return increment.intValue();


        //1 根据behave值 behave=1 修改属性likeCount behave=2 修改favoriteCount behave=3 修改accessCount
        //2.局部数据更新 底层API
        //2.1 UpdateRequest
        /*UpdateRequest updateRequest=new UpdateRequest();
        //2.2 变化数据 判断behave 更新 叠加结果 accessCount likeCount
        updateRequest.index("lbs_article");
        updateRequest.id(id.toString());
        Map<String,Object> updateSource=new HashMap<>();
        if (behave==1){
            updateSource.put("likeCount",(nowCount==null?0:nowCount)+1);
        }else if (behave==2){
            updateSource.put("favoriteCount",(nowCount==null?0:nowCount)+1);
        }else if (behave==3){
            updateSource.put("accessCount",(nowCount==null?0:nowCount)+1);
        }else{
            throw new BusinessException("-2","不支持的行为类型");
        }
        updateRequest.doc(updateSource);
        //2.3 发起请求 修改lbs_article中的数据
        try{
            restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        }catch (Exception e){
            e.printStackTrace();
            throw new BusinessException("-2","更新文章count失败");
        }
        return (nowCount==null?0:nowCount)+1;*/
    }

    private void assembleAndSaveAction(Integer userId, ArticleSearchEntity entity, Integer behave) {
        //[补充] 记录该用户 访问了当前文章 行为是behave=3
        ActionSearchEntity actionEntity=new ActionSearchEntity();
        actionEntity.setArticleId(entity.getId());
        actionEntity.setArticleUserId(entity.getUserId());
        actionEntity.setBehaviorUserId(userId);
        actionEntity.setBehavior(behave);
        actionEntity.setCreateTime(new Date());
        actionEntity.setArticleLabel(entity.getArticleLabel());
        actionEntity.setArticleTitle(entity.getArticleTitle());
        actionESRepository.save(actionEntity);
    }

    public void behave(ArticleActionParam param) throws BusinessException {
        //1.更新原文章 记录的不同count
        ArticleSearchEntity entity = articleESRepository.findById(param.getId()).get();
        countAdd(param.getBehavior(),param.getId(),null);
        //2.根据行为发起请求的参数 封装action写入 行为索引
        assembleAndSaveAction(getUserId(),entity,param.getBehavior());
    }

    public ArticleActionPageResultVO actionPage(ArticleQuery query) throws BusinessException, ExecutionException, InterruptedException {
        long startTime = System.currentTimeMillis();
        //1.拿到一个userId 表示登录用户
        Integer userId = getUserId();
        //2.调用 封装的方法 根据需求 查询不同动作的文章列表
        CompletableFuture<PageResult<ArticleActionVO>> likeFuture=
                CompletableFuture.supplyAsync(()->{
            return selectBehaveArticles(userId,1);
        });
        CompletableFuture<PageResult<ArticleActionVO>> favorFuture=
                CompletableFuture.supplyAsync(()->{
                    return selectBehaveArticles(userId,2);
                });
        CompletableFuture<PageResult<ArticleActionVO>> accessFuture=
                CompletableFuture.supplyAsync(()->{
                    return selectBehaveArticles(userId,3);
                });
        CompletableFuture.allOf(likeFuture,favorFuture,accessFuture).join();
        PageResult<ArticleActionVO> likeVos = likeFuture.get();
        PageResult<ArticleActionVO> favorVos = favorFuture.get();
        PageResult<ArticleActionVO> accessVos = accessFuture.get();
        //3.填补三个属性
        ArticleActionPageResultVO vo=new ArticleActionPageResultVO();
        vo.setLikeFuturePageResult(likeVos);
        vo.setCollectFutureOPageResult(favorVos);
        vo.setCommentFuturePageResult(accessVos);
        long endTime = System.currentTimeMillis();
        log.info("查询用户:{}的行为列表耗时:{}ms",userId,(endTime-startTime));
        return vo;
    }

    private PageResult<ArticleActionVO> selectBehaveArticles(Integer userId, Integer behave) {
        //搜索查询封装的逻辑
        //1.先试用 userId behave的值 构造一个bool(where userId=? and behave=?) 查询行为记录
        //查询到一批 articleId重复的数据 需要去重 List<Integer> articleIds
        List<ActionSearchEntity> actions=articleRepository.selectActionArticles(userId,behave);
        //2.批量获取 利用id查询文章文档信息 最终封装ArticleActionVO返回
        //装配pageResult
        return assemblePageResult(actions);
    }

    private PageResult<ArticleActionVO> assemblePageResult(List<ActionSearchEntity> actions) {
        PageResult<ArticleActionVO> pageResult=new PageResult<>(10,1);
        pageResult.setTotal(100L);
        List<ArticleActionVO> vos=null;
        if(CollectionUtils.isNotEmpty(actions)){
            vos=actions.stream().map(action->{
                ArticleActionVO vo=new ArticleActionVO();
                vo.setId(action.getId());
                vo.setArticleCover(articleESRepository.findById(action.getArticleId()).get().getArticleCover());
                vo.setArticleId(action.getArticleId());
                vo.setArticleTitle(action.getArticleTitle());
                vo.setCreateTime(action.getCreateTime().toLocaleString());
                return vo;
            }).collect(Collectors.toList());
        }
        pageResult.setObjects(vos);
        return pageResult;
    }
}
